package com.xiaofu.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xiaofu.enums.BizCodeEnum;
import com.xiaofu.enums.CouponCategoryEnum;
import com.xiaofu.enums.CouponPublishEnum;
import com.xiaofu.enums.CouponStateEnum;
import com.xiaofu.exception.BizException;
import com.xiaofu.interceptor.LoginInterceptor;
import com.xiaofu.mapper.CouponMapper;
import com.xiaofu.mapper.CouponRecordMapper;
import com.xiaofu.model.CouponDO;
import com.xiaofu.model.CouponRecordDO;
import com.xiaofu.model.LoginUser;
import com.xiaofu.request.NewUserCouponRequest;
import com.xiaofu.service.CouponService;
import com.xiaofu.utils.CommonUtil;
import com.xiaofu.utils.JsonData;
import com.xiaofu.vo.CouponVO;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 小夫
 * @since 2024-07-18
 */
@Service
@Slf4j
public class CouponServiceImpl implements CouponService {

    @Autowired
    private CouponMapper couponMapper;

    @Autowired
    private CouponRecordMapper couponRecordMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public Map<String, Object> pageCouponActivity(int page, int size) {
        Page<CouponDO> pageInfo = new Page(page, size);
        IPage<CouponDO> couponDOPage = couponMapper.selectPage(pageInfo, new LambdaQueryWrapper<CouponDO>()
                .eq(CouponDO::getPublish, CouponPublishEnum.PUBLISH)
                .eq(CouponDO::getCategory, CouponCategoryEnum.PROMOTION)
                .orderByDesc(CouponDO::getCreateTime));

        Map<String, Object> pageMap = new HashMap<>(3);
        pageMap.put("total_record", couponDOPage.getTotal());
        pageMap.put("current_data", beansProcess(couponDOPage.getRecords()));
        pageMap.put("total_page", couponDOPage.getPages());
        return pageMap;
    }

    /**
     * * 1. 获取优惠券是否存在
     * * 2. 校验优惠券是否可以领取
     * * 3. 扣减库存
     * * 4. 保存领券记录
     *
     * @param couponId
     * @param categoryEnum
     * @return
     */
    @Override
    @Transactional(rollbackFor=Exception.class,propagation= Propagation.REQUIRED)
    public JsonData addCoupon(Long couponId, CouponCategoryEnum categoryEnum,LoginUser loginUser) {

        String lockKey = "lock:coupon:" + couponId;
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        log.info("领券接口加锁成功:{}", Thread.currentThread().getId());

        try {
            CouponDO couponDO = couponMapper.selectOne(new LambdaQueryWrapper<CouponDO>()
                    .eq(CouponDO::getId, couponId)
                    .eq(CouponDO::getCategory, categoryEnum.name()));

            // 优惠券是否可以领取
            checkCoupon(couponDO, loginUser.getId());

            // 构建领券记录
            CouponRecordDO couponRecordDO = new CouponRecordDO();
            BeanUtils.copyProperties(couponDO, couponRecordDO);
            couponRecordDO.setCreateTime(new Date());
            couponRecordDO.setUseState(CouponStateEnum.NEW.name());
            couponRecordDO.setUserId(loginUser.getId());
            couponRecordDO.setUserName(loginUser.getName());
            couponRecordDO.setCouponId(couponId);
            couponRecordDO.setId(null);

            // 扣减库存
            int row = couponMapper.reduceStock(couponId);

            if (row == 1) {
                // 库存扣减成功保存记录
                couponRecordMapper.insert(couponRecordDO);
            } else {
                log.warn("发放优惠券失败: {},用户: {}", couponDO, loginUser);
                throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
            }
        } finally {
            lock.unlock();
            log.info("解锁成功");
        }

        return JsonData.buildSuccess();
    }

    @Override
    @Transactional(rollbackFor=Exception.class,propagation= Propagation.REQUIRED)
    public JsonData addCoupon(Long couponId, CouponCategoryEnum categoryEnum) {
        LoginUser loginUser = LoginInterceptor.threadLocal.get();
        return addCoupon(couponId, categoryEnum, loginUser);
    }


    public JsonData addCoupon1(Long couponId, CouponCategoryEnum categoryEnum) {
        LoginUser loginUser = LoginInterceptor.threadLocal.get();

        // 使用LUA脚本
        String uuid = CommonUtil.generateUUID();
        String lockKey = "lock:coupon:" + couponId;
        Boolean lockFlag = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, Duration.ofSeconds(30));
        if (lockFlag) {
            // 加锁成功
            log.info("加锁成功:{}", couponId);
            try {
                // 执行业务逻辑 TODO


                CouponDO couponDO = couponMapper.selectOne(new LambdaQueryWrapper<CouponDO>()
                        .eq(CouponDO::getId, couponId)
                        .eq(CouponDO::getCategory, categoryEnum.name()));

                // 优惠券是否可以领取
                checkCoupon(couponDO, loginUser.getId());

                // 构建领券记录
                CouponRecordDO couponRecordDO = new CouponRecordDO();
                BeanUtils.copyProperties(couponDO, couponRecordDO);
                couponRecordDO.setCreateTime(new Date());
                couponRecordDO.setUseState(CouponStateEnum.NEW.name());
                couponRecordDO.setUserId(loginUser.getId());
                couponRecordDO.setUserName(loginUser.getName());
                couponRecordDO.setCouponId(couponId);
                couponRecordDO.setId(null);

                // 扣减库存
                int row = couponMapper.reduceStock(couponId);

                if (row == 1) {
                    // 库存扣减成功保存记录
                    couponRecordMapper.insert(couponRecordDO);
                } else {
                    log.warn("发放优惠券失败: {},用户: {}", couponDO, loginUser);
                    throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
                }
            } finally {
                String script = " if redis.call('get',KEYS[1]) == ARGV[1] then " +
                        "   return redis.call('del',KEYS[1]) " +
                        " else" +
                        "   return 0 " +
                        " end";
                Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(lockKey), uuid);
                log.info("解锁: {}", result);
            }
        } else {
            // 加锁失败
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                log.error("自旋失败");
            }
            addCoupon1(couponId, categoryEnum);
        }

        return JsonData.buildSuccess();
    }

    /**
     * 校验是否可以领取
     *
     * @param couponDO
     * @param userId
     */
    private void checkCoupon(CouponDO couponDO, Long userId) {

        if (couponDO == null) {
            throw new BizException(BizCodeEnum.COUPON_NO_EXITS);
        }

        // 库存是否足够
        if (couponDO.getStock() <= 0) {
            throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
        }

        // 判断是否发布状态
        if (!couponDO.getPublish().equals(CouponPublishEnum.PUBLISH.name())) {
            throw new BizException(BizCodeEnum.COUPON_GET_FAIL);
        }

        // 是否在领取时间范围内
        long time = CommonUtil.getCurrentTimeMillis();
        long start = couponDO.getStartTime().getTime();
        long end = couponDO.getEndTime().getTime();
        if (time < start || time > end) {
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_TIME);
        }

        // 用户是否超过限制
        Integer recordNum = couponRecordMapper.selectCount(new LambdaQueryWrapper<CouponRecordDO>()
                .eq(CouponRecordDO::getCouponId, couponDO.getId())
                .eq(CouponRecordDO::getUserId, userId)
        );
        if (recordNum >= couponDO.getUserLimit()) {
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_LIMIT);
        }
    }


    /**
     * 用户微服务调用的时候,没有传递token
     * @param newUserCouponRequest
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public JsonData initNewUserCoupon(NewUserCouponRequest newUserCouponRequest) {
        LoginUser loginUser = new LoginUser();
        loginUser.setId(newUserCouponRequest.getUserId());
        loginUser.setName(newUserCouponRequest.getName());

        // 查询新用户有哪些优惠券
        List<CouponDO> couponDOS = couponMapper.selectList(new LambdaQueryWrapper<CouponDO>()
                .eq(CouponDO::getCategory, CouponCategoryEnum.NEW_USER.name()));
        for (CouponDO couponDO : couponDOS) {
            // 幂等操作需要加锁
            this.addCoupon(couponDO.getId(),CouponCategoryEnum.NEW_USER,loginUser);
        }
        return JsonData.buildSuccess();
    }

    private List<CouponVO> beansProcess(List<CouponDO> records) {
        return records.stream().map(item -> {
            CouponVO couponVO = new CouponVO();
            BeanUtils.copyProperties(item, couponVO);
            return couponVO;
        }).collect(Collectors.toList());
    }
}
